<?php

/*
 * Copyright (c) 2012-2013 Aalto University and University of Helsinki
 * MIT License
 * see LICENSE.txt for more information
 */

class Vocabulary extends DataObject {

  public function getId() {
    $uriparts = explode("#", $this->resource->getURI());
    if (count($uriparts) != 1)
    // hash namespace
      return $uriparts[1];
    // slash namespace
    $uriparts = explode("/", $voc->getURI());
    return $uriparts[count($uriparts) - 1];
  }

  public function getTitle() {
    $literal = $this->resource->getLiteral('dc:title', $this->lang);
    if ($literal)
      return $literal->getValue();
    // not found with selected language, try any language
    return $this->resource->getLiteral('dc:title')->getValue();
  }
  
  /**
   * Get the color for this vocabulary as as a CSS3 color definition string
   * (see http://www.w3.org/TR/css3-color/ )
   * @return string color definition
   */
  
  public function getColor() {
    $id = $this->getId();
    $hash = hexdec(substr(md5($id),0,8));
    $hue = floor($hash % 40) * 9;
    $sat = 100;
    $light = 50;
    $rgb = $this->fGetRGB($hue, $sat, $light);
    return "$rgb";
  }

  function fGetRGB($iH, $iS, $iV) {

    if($iH < 0) $iH = 0; // Hue:
    if($iH > 360) $iH = 360; // 0-360
    if($iS < 0) $iS = 0; // Saturation:
    if($iS > 100) $iS = 100; // 0-100
    if($iV < 0) $iV = 0; // Lightness:
    if($iV > 100) $iV = 100; // 0-100

    $dS = $iS/100.0; // Saturation: 0.0-1.0
    $dV = $iV/100.0; // Lightness: 0.0-1.0
    $dC = $dV*$dS; // Chroma: 0.0-1.0
    $dH = $iH/60.0; // H-Prime: 0.0-6.0
    $dT = $dH; // Temp variable

    while($dT >= 2.0) $dT -= 2.0; // php modulus does not work with float
    $dX = $dC*(1-abs($dT-1)); // as used in the Wikipedia link

    switch($dH) {
    case($dH >= 0.0 && $dH < 1.0):
      $dR = $dC; $dG = $dX; $dB = 0.0; break;
    case($dH >= 1.0 && $dH < 2.0):
      $dR = $dX; $dG = $dC; $dB = 0.0; break;
    case($dH >= 2.0 && $dH < 3.0):
      $dR = 0.0; $dG = $dC; $dB = $dX; break;
    case($dH >= 3.0 && $dH < 4.0):
      $dR = 0.0; $dG = $dX; $dB = $dC; break;
    case($dH >= 4.0 && $dH < 5.0):
      $dR = $dX; $dG = 0.0; $dB = $dC; break;
    case($dH >= 5.0 && $dH < 6.0):
      $dR = $dC; $dG = 0.0; $dB = $dX; break;
    default:
      $dR = 0.0; $dG = 0.0; $dB = 0.0; break;
    }

    $dM = $dV - $dC;
    $dR += $dM; $dG += $dM; $dB += $dM;
    $dR *= 255; $dG *= 255; $dB *= 255;

    return round($dR).",".round($dG).",".round($dB);
  }
  /**
   * Get the languages supported by this vocabulary
   * @return array languages supported by this vocabulary (as language tag strings)
   */
  public function getLanguages() {
    $langs = $this->resource->allLiterals('onki:language');
    $ret = array();
    foreach ($langs as $lang) {
      $ret[] = $lang->getValue();
    }
    return $ret;
  }
  
  /**
   * Get the default language of this vocabulary
   * @return string default language, e.g. 'en'
   */
  
  public function getDefaultLanguage() {
    $deflang = $this->resource->getLiteral('onki:defaultLanguage');
    if ($deflang) return $deflang->getValue();
    $langs = $this->getLanguages();
    if (sizeof($langs) > 1)
      trigger_error("Default language for vocabulary '" . $this->getId() . "' unknown, choosing '$langs[0]'.", E_USER_WARNING);
    return $langs[0];
  }

  /**
   * Get the SPARQL endpoint URL for this vocabulary
   *
   * @return string endpoint URL
   */
  public function getEndpoint() {
    return $this->resource->get('void:sparqlEndpoint')->getUri();
  }

  /**
   * Get the SPARQL graph URI for this vocabulary
   *
   * @return string graph URI
   */
  public function getGraph() {
    $graph = $this->resource->get('onki:sparqlGraph');
    if ($graph)
      $graph = $graph->getUri();
    return $graph;
  }

  /**
   * Get the SPARQL implementation for this vocabulary
   *
   * @return Sparql SPARQL object
   */
  public function getSparql() {
    $endpoint = $this->getEndpoint();
    $graph = $this->getGraph();
    $dialect = $this->resource->get('onki:sparqlDialect');
    $dialect = $dialect ? $dialect->getValue() : DEFAULT_SPARQL_DIALECT;
    return $this->model->getSparqlImplementation($dialect, $endpoint, $graph);
  }

  /**
   * Get the URI space of concepts in this vocabulary.
   *
   * @return string full URI of concept
   */
  public function getUriSpace() {
    return $this->resource->getLiteral('void:uriSpace')->getValue();
  }

  /**
   * Get the full URI of a concept in a vocabulary. If the passed local
   * name is already a full URI, return it unchanged.
   *
   * @param $lname string local name of concept
   * @return string full URI of concept
   */
  public function getConceptURI($lname) {
    if (strpos($lname, 'http') === 0) return $lname; // already a full URI
    return $this->getUriSpace() . $lname;
  }
  
  public function getConceptLabel($uri, $lang) {
    return $this->getSparql()->queryLabel($uri,$lang);
  }

  /**
   * Get the localname of a concept in the vocabulary. If the URI is not
   * in the URI space of this vocabulary, return the full URI.
   *
   * @param $lname string full URI of concept
   * @return string local name of concept, or original full URI if the local name cannot be determined
   */
  public function getLocalName($uri) {
    return str_replace($this->getUriSpace(), "", $uri);
  }

  /**
   * Retrieves all the information about the Vocabulary
   * from the SPARQL-endpoint.
   */
  public function getInfo() {
    $ret = array();

    // get metadata from vocabulary configuration file
    foreach ($this->resource->properties() as $prop) {
      foreach ($this->resource->allLiterals($prop, $this->lang) as $val) {
        $ret[$prop][] = $val->getValue();
      }
      foreach ($this->resource->allResources($prop) as $val) {
        $label = $val->label($this->lang);
        if ($label) {
          $ret[$prop][] = $label->getValue();
        }
      }
    }

    // also include ConceptScheme metadata from SPARQL endpoint
    $cs = $this->getDefaultConceptScheme();

    // query everything the endpoint knows about the ConceptScheme
    $sparql = $this->getSparql();
    $result = $sparql->queryConceptScheme($cs);
    $cs = $result->resource($cs);

    foreach ($cs->properties() as $prop) {
      foreach ($cs->allLiterals($prop, $this->lang) as $val) {
        $ret[$prop][] = $val->getValue();
      }
      if (!isset($ret[$prop]) || sizeof($ret[$prop]) == 0) { // not found with language tag
        foreach ($cs->allLiterals($prop, null) as $val) {
          $ret[$prop][] = $val->getValue();
        }
      }
      foreach ($cs->allResources($prop) as $val) {
        $label = $val->label($this->lang);
        if ($label) {
          $ret[$prop][] = $label->getValue();
        } else {
          $ret[$prop][] = $val->getURI();
        }
      }
    }
    if (isset($ret['owl:versionInfo'])) { // if version info availible for vocabulary convert it to a more readable format
      $ret['owl:versionInfo'][0] = $this->parseVersionInfo($ret['owl:versionInfo'][0]);
    }
    // remove duplicate values
    foreach (array_keys($ret) as $prop)
      $ret[$prop] = array_unique($ret[$prop]);
    
    return $ret;
  }

  /**
   * Return all types (RDFS/OWL classes) present in the vocabulary.
   * @return array Array with URIs (string) as key and array of (label, superclassURI) as value
   */
  
  public function getTypes() {
    return $this->getSparql()->queryTypes($this->lang);
  }

  /**
   * Return all concept schemes in the vocabulary.
   * @return array Array with concept scheme URIs (string) as keys and labels (string) as values
   */
  
  public function getConceptSchemes() {
    return $this->getSparql()->queryConceptSchemes($this->lang);
  }
  
  /**
   * Return the URI of the default concept scheme of this vocabulary. If the onki:mainConceptScheme property is set in the
   * vocabulary configuration, that will be returned. Otherwise an arbitrary concept scheme will be returned.
   * @return string concept scheme URI
   */
  
  public function getDefaultConceptScheme() {
    $conceptScheme = $this->resource->get("onki:mainConceptScheme");
    if ($conceptScheme) return $conceptScheme->getUri();

    // mainConceptScheme not explicitly set, guess it
    foreach ($this->getConceptSchemes() as $uri => $csdata) {
      $conceptScheme = $uri; // actually pick the last one
    }
    return $conceptScheme;
  }

  /**
   * Return the top concepts of a concept scheme in the vocabulary.
   * @param string $conceptScheme URI of concept scheme whose top concepts to return. If not set,
   *                              the default concept scheme of the vocabulary will be used.
   * @return array Array with concept URIs (string) as keys and labels (string) as values
   */

  public function getTopConcepts($conceptScheme=null) {
    if (!$conceptScheme)
      $conceptScheme = $this->getDefaultConceptScheme();
    return $this->getSparql()->queryTopConcepts($conceptScheme, $this->lang);
  }
  

  /**
   * Tries to parse version, date and time from sparql version information into a readable format.
   * @param string $version
   * @return string
   */
  private function parseVersionInfo($version) {
    $parts = explode(' ', $version);
    if ($parts[0] != '$Id:') return $version; // don't know how to parse
    $rev = $parts[2];
    $datestr = $parts[3] . ' ' . $parts[4];
    return "$datestr (r$rev)";
  }

  /**
   * Counts the statistics of the vocabulary.
   * @return type 
   */
  public function getStatistics() {
    $sparql = $this->getSparql();
    $ret = array();
    // find the number of concepts
    $ret['concepts'] = $sparql->countConcepts();
    // count the number of different types of concepts in all languages
    $ret['terms'] = $sparql->countLangConcepts($this->getLanguages());
    return $ret;
  }

  /** 
   * get the URL from which the vocabulary data can be downloaded
   */
  function getDataURL() {
    $val = $this->resource->getResource("void:dataDump");
    if ($val)
      return $val->getURI();
    return false;
  }

  function getHasGlobalCollections() {
    $val = $this->resource->getLiteral("onki:globalCollections");
    if ($val)
      return (boolean) $val->getValue();
    return false;
  }

  
  public function getConceptHierarchy($uri) {
    return $this->getSparql()->queryParentList($uri, $this->lang);
  }
  
  public function getConceptChildren($uri) {
    return $this->getSparql()->queryChildren($uri, $this->lang);
  }
  
  public function getConceptNarrowers($uri) {
    return $this->getSparql()->queryProperty($uri, 'skos:narrower', $this->lang);
  }
  
  public function getConceptTransitiveNarrowers($uri, $limit) {
    return $this->getSparql()->queryTransitiveProperty($uri, 'skos:narrower',$this->lang,$limit);
  }

  public function getConceptBroaders($uri, $lang='fi') {
    return $this->getSparql()->queryProperty($uri, 'skos:broader', $this->lang);
  }
  
  public function getConceptTransitiveBroaders($uri, $limit) {
    return $this->getSparql()->queryTransitiveProperty($uri, 'skos:broader', $this->lang, $limit);
  }
  
  public function getConceptRelateds($uri) {
    return $this->getSparql()->queryProperty($uri,'skos:related', $this->lang);
  }

  /**
   * returns concept's RDF in downloadable format
   * @param string $lname is concept's 'local name' i.e. 'p12345'
   * @param string $format is the format in which you want to get the result, currently this function supports
   * text/turtle, application/rdf+xml and application/json
   */
  public function getRDF($uri, $format) {
    $sparql = $this->getSparql();

    if ($format == 'text/turtle') {
      $retform = 'turtle';
      $serialiser = new EasyRdf_Serialiser_Turtle();
    } elseif ($format == 'application/ld+json' || $format == 'application/json') {
      $retform = 'jsonld'; // serve JSON-LD for both JSON-LD and plain JSON requests
      $serialiser = new EasyRdf_Serialiser_JsonLd();
    } else {
      $retform = 'rdfxml';
      $serialiser = new EasyRdf_Serialiser_RdfXml();
    }
    
    $result = $sparql->queryConceptInfo($uri, $this->getHasGlobalCollections(), null, null, true);
    return $serialiser->serialise($result, $retform);
  }

  /**
   * Makes a query into the sparql endpoint for a concept.
   * @param string $uri the full URI of the concept
   * @return array
   */
  public function getConceptInfo($uri) {
    $sparql = $this->getSparql();
    return $sparql->queryConceptInfo($uri, $this->getHasGlobalCollections(), $this->lang);
  }
  
  public function listCollections() {
    $collections = $this->getSparql()->listCollections($this->lang);
    $ret = array();
    foreach ($collections as $uri => $label) {
      $ret[$uri] = $label;
    }
    return $ret;
  }
  
  public function listCollectionContents($clname) {
    $collection = $this->getConceptURI($clname);
    $contents = $this->getSparql()->listCollectionContents($collection, $this->lang);
    $ret = array();
    foreach ($contents as $uri => $label) {
      $ret[$uri] = $label;
    }
    return $ret;
  }
  
}
